Skip to main content

第 1 章:Docker 進階網路設定

Docker 網路相關命令列表

其中有些命令選項只有在 Docker 服務啟動的時候才能設定,而且不能馬上生效

  • b BRIDGE or --bridge=BRIDGE --指定容器掛載的橋接器
  • -bip=CIDR --定制 docker0 的遮罩
  • H SOCKET... or --host=SOCKET... --Docker 服務端接收命令的通道
  • -icc=true|false --是否支援容器之間進行通訊
  • -ip-forward=true|false --請看下文容器之間的通訊
  • -iptables=true|false --禁止 Docker 新增 iptables 規則
  • -mtu=BYTES --容器網路中的 MTU

下面 2 個命令選項既可以在啟動服務時指定,也可以 Docker 容器啟動(docker run)時候指定

  • -dns=IP_ADDRESS... --使用指定的DNS伺服器
  • -dns-search=DOMAIN... --指定DNS搜尋域

最後這些選項只有在 docker run 執行時使用,因為它是針對容器的特性內容。

  • h HOSTNAME or --hostname=HOSTNAME --設定容器主機名
  • -link=CONTAINER_NAME:ALIAS --新增到另一個容器的連線
  • -net=bridge|none|container:NAME_or_ID|host --設定容器的橋接模式
  • p SPEC or --publish=SPEC --映射容器連接埠到宿主主機
  • P or --publish-all=true|false --映射容器所有連接埠到宿主主機

設定 DNS

Docker 沒有為每個容器專門定制映像檔 → 利用虛擬檔案來掛載到來容器的 3 個相關設定檔案

mount
...
/dev/disk/by-uuid/1fec...ebdf on /etc/hostname type ext4 ...
/dev/disk/by-uuid/1fec...ebdf on /etc/hosts type ext4 ...
tmpfs on /etc/resolv.conf type tmpfs ...
...

讓主機 DNS 訊息發生更新後,所有容器的 dns 設定透過 /etc/resolv.conf 檔案立刻得到更新

如果使用者想要手動指定容器的設定,可以利用下面的選項。

  • h HOSTNAME or --hostname=HOSTNAME 設定容器的主機名,它會被寫到容器內的 /etc/hostname/etc/hosts。但它在容器外部看不到,既不會在 docker ps 中顯示,也不會在其他的容器的 /etc/hosts 看到。
  • -link=CONTAINER_NAME:ALIAS 選項會在建立容器的時候,新增一個其他容器的主機名到 /etc/hosts 檔案中,讓新容器的程式可以使用主機名 ALIAS 就可以連線它。
  • -dns=IP_ADDRESS 新增 DNS 伺服器到容器的 /etc/resolv.conf 中,讓容器用這個伺服器來解析所有不在 /etc/hosts 中的主機名。
  • -dns-search=DOMAIN 設定容器的搜尋域,當設定搜尋域為 .example.com 時,在搜尋一個名為 host 的主機時,DNS 不僅搜尋host,還會搜尋 host.example.com。 注意:如果沒有上述最後 2 個選項,Docker 會預設用主機上的 /etc/resolv.conf 來設定容器

容器存取控制

容器的存取控制,主要透過 Linux 上的 iptables 防火墻來進行管理和實作。iptables 是 Linux 上預設的防火墻軟體,在大部分發行版中都內建。

容器存取外部網路

容器要想存取外部網路,需要本地系統的轉發支援。在Linux 系統中,檢查轉發是否打開。

sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1

如果為 0,說明沒有開啟轉發,則需要手動打開。

sysctl -w net.ipv4.ip_forward=1

如果在啟動 Docker 服務的時候設定 --ip-forward=true , Docker 就會自動設定系統的 ip_forward 參數為 1

容器之間存取

容器之間相互存取,需要兩方面的支援。

容器的網路拓撲是否已經互聯。預設情況下,所有容器都會被連線到 docker0 橋接器上。

本地系統的防火墻軟體 -- iptables 是否允許透過。

存取所有連接埠

當啟動 Docker 服務時候,預設會新增一條轉發策略到 iptables 的 FORWARD 鏈上。策略為透過(ACCEPT)還是禁止(DROP)取決於設定--icc=true(預設值)還是 --icc=false。當然,如果手動指定 --iptables=false 則不會新增 iptables 規則。

可見,預設情況下,不同容器之間是允許網路互通的。如果為了安全考慮,可以在 /etc/default/docker 檔案中設定 DOCKER_OPTS=--icc=false 來禁止它。

存取指定連接埠

在透過 -icc=false 關閉網路存取後,還可以透過 --link=CONTAINER_NAME:ALIAS 選項來存取容器的開放連接埠。

例如,在啟動 Docker 服務時,可以同時使用 icc=false --iptables=true 參數來關閉允許相互的網路存取,並讓 Docker 可以修改系統中的 iptables 規則。

此時,系統中的 iptables 規則可能是類似

sudo iptables -nL
...
Chain FORWARD (policy ACCEPT)
target prot opt source destination
DROP all -- 0.0.0.0/0 0.0.0.0/0
...

之後,啟動容器(docker run)時使用 --link=CONTAINER_NAME:ALIAS 選項。Docker 會在 iptable 中為 兩個容器分別新增一條 ACCEPT 規則,允許相互存取開放的連接埠(取決於 Dockerfile 中的 EXPOSE 行)。

當新增了 --link=CONTAINER_NAME:ALIAS 選項後,新增了 iptables 規則。

sudo iptables -nL
...
Chain FORWARD (policy ACCEPT
target prot opt source destination
ACCEPT tcp -- 172.17.0.2 172.17.0.3 tcp spt:80
ACCEPT tcp -- 172.17.0.3 172.17.0.2 tcp dpt:80
DROP all -- 0.0.0.0/0 0.0.0.0/0
...

注意:--link=CONTAINER_NAME:ALIAS 中的 CONTAINER_NAME 目前必須是 Docker 分配的名字,或使用 --name 參數指定的名字。主機名則不會被識別。

埠號映射實作

預設情況下,容器可以主動存取到外部網路的連線,但是外部網路無法存取到容器。

容器存取外部實作

容器所有到外部網路的連線,源位址都會被NAT成本地系統的IP位址。這是使用 iptables 的源位址偽裝操作實作的。

sudo iptables -t nat -nL

...
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 !172.17.0.0/16
...

其中,上述規則將所有源位址在 172.17.0.0/16 網段,目標位址為其他網段(外部網路)的流量動態偽裝為從系統網卡發出。MASQUERADE 跟傳統 SNAT 的好處是它能動態從網卡取得位址。

外部存取容器實作

容器允許外部存取,可以在 docker run 時候透過 -p-P 參數來啟用。

不管用那種辦法,其實也是在本地的 iptable 的 nat 表中新增相應的規則。

使用 -P 時:

iptables -t nat -nL

...
Chain DOCKER (2 references)
target prot opt source destination
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:49153 to:172.17.0.2:80

使用 -p 80:80

iptables -t nat -nL

Chain DOCKER (2 references)
target prot opt source destination
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80

這裡的規則映射了 0.0.0.0,意味著將接受主機來自所有介面的流量。使用者可以透過 -p IP:host_port:container_port-p IP::port 來指定允許存取容器的主機上的 IP、介面等,以制定更嚴格的規則。

如果希望永久綁定到某個固定的 IP 位址,可以在 Docker 設定檔案 /etc/default/docker 中指定 DOCKER_OPTS="--ip=IP_ADDRESS",之後重啟 Docker 服務即可生效。

設定 docker0 橋接器

Docker 服務預設會建立一個 docker0 橋接器(其上有一個 docker0 內部介面),它在核心層連通了其他的物理或虛擬網卡,這就將所有容器和本地主機都放到同一個物理網路。

Docker 預設指定了 docker0 介面 的 IP 位址和子網遮罩,讓主機和容器之間可以透過橋接器相互通訊,它還給出了 MTU(介面允許接收的最大傳輸單元),通常是 1500 Bytes,或宿主主機網路路由上支援的預設值。這些值都可以在服務啟動的時候進行設定。

  • -bip=CIDR -- IP 位址加遮罩格式,例如 192.168.1.5/24
  • -mtu=BYTES -- 覆蓋預設的 Docker mtu 設定

也可以在設定檔案中設定 DOCKER_OPTS,然後重啟服務。 由於目前 Docker 橋接器是 Linux 橋接器,用者可以使用 brctl show 來查看橋接器和連接埠連線訊息。

sudo brctl show

bridge name bridge id STP enabled interfaces
docker0 8000.3a1d7362b4ee no veth65f9 vethdda6

brctl 命令在 Debian、Ubuntu 中可以使用 sudo apt-get install bridge-utils 來安裝。

每次建立一個新容器的時候,Docker 從可用的位址段中選擇一個未使用的 IP 位址分配給容器的 eth0 連接埠。使用本地主機上 docker0 介面的 IP 作為所有容器的預設網關。

sudo docker run -i -t --rm base /bin/bash

ip addr show eth0
24: eth0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 32:6f:e0:35:57:91 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.3/16 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::306f:e0ff:fe35:5791/64 scope link
valid_lft forever preferred_lft forever

ip route

default via 172.17.42.1 dev eth0
172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.3

exit

自訂橋接器

除了預設的 docker0 橋接器,使用者也可以指定橋接器來連線各個容器。

在啟動 Docker 服務的時候,使用 -b BRIDGE--bridge=BRIDGE 來指定使用的橋接器。

如果服務已經執行,那需要先停止服務,並刪除舊的橋接器。

sudo service docker stop
sudo ip link set dev docker0 down
sudo brctl delbr docker0

然後建立一個橋接器 bridge0

sudo brctl addbr bridge0
sudo ip addr add 192.168.5.1/24 dev bridge0
sudo ip link set dev bridge0 up

查看確認橋接器建立並啟動。

ip addr show bridge0

4: bridge0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state UP group default
link/ether 66:38:d0:0d:76:18 brd ff:ff:ff:ff:ff:ff
inet 192.168.5.1/24 scope global bridge0
valid_lft forever preferred_lft forever

設定 Docker 服務,預設橋接到建立的橋接器上。

echo 'DOCKER_OPTS="-b=bridge0"' >> /etc/default/docker
sudo service docker start

啟動 Docker 服務。 新建一個容器,可以看到它已經橋接到了 bridge0 上。

可以繼續用 brctl show 命令查看橋接的訊息。另外,在容器中可以使用 ip addrip route 命令來查看 IP 位址設定和路由訊息。

pipework

Jérôme Petazzoni 編寫了一個叫 pipework 的 shell 腳本,可以幫助使用者在比較復雜的場景中完成容器的連線

playground

Brandon Rhodes 建立了一個提供完整的 Docker 容器網路拓撲管理的 Python庫,包括路由、NAT 防火墻;以及一些提供 HTTP 、 SMTP 、 POP 、 IMAP 、 Telnet 、 SSH 、 FTP 的伺服器。

編輯網路設定檔案

Docker 1.2.0 開始支援在執行中的容器裡編輯 /etc/hosts/etc/hostname/etc/resolve.conf 檔案。

但是這些修改是臨時的,只在執行的容器中保留,容器終止或重啟後並不會被保存下來。也不會被 docker commit 提交。

範例:創造一個點對點連線

預設情況下,Docker 會將所有容器連線到由 docker0 提供的虛擬子網中。

使用者有時候需要兩個容器之間可以直連通訊,而不用透過主機橋接器進行橋接。

解決辦法很簡單:建立一對 peer 介面,分別放到兩個容器中,設定成點到點鏈路類型即可。

首先啟動 2 個容器:

sudo docker run -i -t --rm --net=none base /bin/bash

找到程式號,然後建立網路命名空間的跟蹤檔案。

sudo docker inspect -f '{{.State.Pid}}' 1f1f4c1f931a

2989

sudo docker inspect -f '{{.State.Pid}}' 12e343489d2f

3004

sudo mkdir -p /var/run/netns
sudo ln -s /proc/2989/ns/net /var/run/netns/2989
sudo ln -s /proc/3004/ns/net /var/run/netns/3004

建立一對 peer 介面,然後設定路由

sudo ip link add A type veth peer name B



sudo ip link set A netns 2989
sudo ip netns exec 2989 ip addr add 10.1.1.1/32 dev A
sudo ip netns exec 2989 ip link set A up
sudo ip netns exec 2989 ip route add 10.1.1.2/32 dev A



sudo ip link set B netns 3004
sudo ip netns exec 3004 ip addr add 10.1.1.2/32 dev B
sudo ip netns exec 3004 ip link set B up
sudo ip netns exec 3004 ip route add 10.1.1.1/32 dev B

現在這 2 個容器就可以相互 ping 通,並成功建立連線。點到點鏈路不需要子網和子網遮罩。

此外,也可以不指定 --net=none 來建立點到點鏈路。這樣容器還可以透過原先的網路來通訊。

利用類似的辦法,可以建立一個只跟主機通訊的容器。但是一般情況下,更推薦使用 --icc=false 來關閉容器之間的通訊。

多台實體主機之間的容器互連

Docker 預設的橋接網卡是 docker0。它只會在本機橋接所有的容器網卡,舉例來說容器的虛擬網卡在主機上看一般叫做 veth* 而 Docker 只是把所有這些網卡橋接在一起

brctl show

bridge name bridge id STP enabled interfaces
docker0 8000.56847afe9799 no veth0889 veth3c7bveth4061

在容器中看到的位址一般是像下面這樣的位址

ip a

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
11: eth0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 4a:7d:68:da:09:cf brd ff:ff:ff:ff:ff:f
inet 172.17.0.3/16 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::487d:68ff:feda:9cf/64 scope lin
valid_lft forever preferred_lft forever

這樣就可以把這個網路看成是一個私有的網路,透過 nat 連線外網,如果要讓外網連線到容器中,就需要做連接埠映射,即 -p 參數。

如果在企業內部應用,或者做多個物理主機的集群,可能需要將多個物理主機的容器組到一個物理網路中來,那麽就需要將這個橋接器橋接到我們指定的網卡上。

範例:多台實體主機 ubuntu 之間的容器互連

以 ubuntu 為例建立多個主機的容器聯網。建立自己的橋接器,編輯 /etc/network/interface 檔案

auto br0
iface br0 inet static
address 192.168.7.31
netmask 255.255.240.0
gateway 192.168.7.254
bridge_ports em1
bridge_stp off
dns-nameservers 8.8.8.8 192.168.6.1

將 Docker 的預設橋接器綁定到這個新建的 br0 上面,這樣就將這臺機器上容器綁定到 em1 這個網卡所對應的物理網路上了。

ubuntu 修改 /etc/default/docker 檔案,新增最後一行內容

# Docker Upstart and SysVinit configuration file
# Customize location of Docker binary (especially for development testing).
#DOCKER="/usr/local/bin/docker
# Use DOCKER_OPTS to modify the daemon startup options.
#DOCKER_OPTS="--dns 8.8.8.8 --dns 8.8.4.4

# If you need Docker to use an HTTP proxy, it can also be specified here.
#export http_proxy="http://127.0.0.1:3128/"

# This is also a handy place to tweak where Docker's temporary files go.
#export TMPDIR="/mnt/bigdrive/docker-tmp"

DOCKER_OPTS="-b=br0"

在啟動 Docker 的時候 使用 -b 參數 將容器綁定到物理網路上。重啟 Docker 服務後,再進入容器可以看到它已經綁定到你的物理網路上了。

docker ps

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
58b043aa05eb desk_hz:v1 "/startup.sh" 5 days ago Up 2 seconds 5900/tcp, 6080/tcp, 22/tcp yanlx

brctl show

bridge name bridge id STP enabled interfaces
br0 8000.7e6e617c8d53 no em1 vethe6e5

這樣就直接把容器暴露到物理網路上了,多臺物理主機的容器也可以相網路了。需要注意的是,這樣就需要自己來保證容器的網路安全了。

ip addr show eth0

24: eth0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 32:6f:e0:35:57:91 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.3/16 scope global eth0
valid_lft forever preferred_lft foreve
inet6 fe80::306f:e0ff:fe35:5791/64 scope link
valid_lft forever preferred_lft forever

ip route

default via 172.17.42.1 dev eth0
172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.3

exit

Docker 的網路實作其實就是利用了 Linux 上的網路命名空間和虛擬網路設備(特別是 veth pair)。建議先熟悉了解這兩部分的基本概念再閱讀本章。

基本原理

首先,要實作網路通訊介面,機器需要至少一個網路介面(物理介面或虛擬介面)來收發資料包;此外,如果不同子網之間要進行通訊,需要路由機制。

Docker 中的網路介面預設都是虛擬的介面。虛擬介面的優勢之一是轉發效率較高。 Linux 透過在核心中進行資料複製來實作虛擬介面之間的資料轉發,發送介面的發送緩存中的資料包被直接複製到接收介面的接收緩存中。對於本地系統和容器內系統看來就像是一個正常的乙太網卡,只是它不需要真正同外部網路設備通訊,速度要快很多。

Docker 容器網路就利用了這項技術。它在本地主機和容器內分別建立一個虛擬介面,並讓它們彼此連通(這樣的一對介面叫做 veth pair)。

建立網路參數

Docker 建立一個容器的時候,會執行以下操作:

建立一對虛擬介面,分別放到本地主機和新容器中;

本地主機一端橋接到預設的 docker0 或指定橋接器上,並具有一個唯一的名字,如 veth65f9;

容器一端放到新容器中,並修改名字作為 eth0,這個介面只在容器的命名空間可見;

從橋接器可用位址段中取得一個未使用位址分配給容器的 eth0,並設定預設路由到橋接網卡 veth65f9。

完成這些之後,容器就可以使用 eth0 虛擬網卡來連線其他容器和其他網路。

可以在 docker run 的時候透過 --net 參數來指定容器的網路設定,有4個可選值:

  • -net=bridge 這個是預設值,連線到預設的橋接器。
  • -net=host 告訴 Docker 不要將容器網路放到隔離的命名空間中,即不要容器化容器內的網路。此時容器使用本地主機的網路,它擁有完全的本地主機介面存取權限。容器程式可以跟主機其它 root 程式一樣可以打開低範圍的連接埠,可以存取本地網路服務比如 D-bus,還可以讓容器做一些影響整個主機系統的事情,比如重啟主機。因此使用這個選項的時候要非常小心。如果進一步的使用 -privileged=true,容器會被允許直接設定主機的網路堆棧。
  • -net=container:NAME_or_ID 讓 Docker 將新建容器的程式放到一個已存在容器的網路堆疊中,新容器程式有自己的檔案系統、程式列表和資源限制,但會和已存在的容器共享 IP 位址和連接埠等網路資源,兩者程式可以直接透過 lo 迴路介面通訊。
  • -net=none 讓 Docker 將新容器放到隔離的網路堆疊中,但是不進行網路設定。之後,使用者可以自己進行設定。

網路設定細節

使用者使用 --net=none 後,可以自行設定網路,讓容器達到跟平常一樣具有存取網路的權限。透過這個過程,可以了解 Docker 設定網路的細節。

首先,啟動一個 /bin/bash 容器,指定 --net=none 參數。

sudo docker run -i -t --rm --net=none base /bin/bas

在本地主機查找容器的程式 id,並為它建立網路命名空間。

sudo docker inspect -f '{{.State.Pid}}' 63f36fc01b5f

277

pid=2778
sudo mkdir -p /var/run/netns
sudo ln -s /proc/$pid/ns/net /var/run/netns/$pid

檢查橋接網卡的 IP 和子網遮罩訊息。

ip addr show docker0
21: docker0: ..
inet 172.17.42.1/16 scope global docker0
...

建立一對 “veth pair” 介面 A 和 B,綁定 A 到橋接器 docker0,並啟用它

sudo ip link add A type veth peer name B
sudo brctl addif docker0 A
sudo ip link set A up

將B放到容器的網路命名空間,命名為 eth0,啟動它並設定一個可用 IP(橋接網段)和預設網關。

sudo ip link set B netns $pid
sudo ip netns exec $pid ip link set dev B name eth0
sudo ip netns exec $pid ip link set eth0 up
sudo ip netns exec $pid ip addr add 172.17.42.99/16 dev eth0
sudo ip netns exec $pid ip route add default via 172.17.42.1

當容器結束後,Docker 會清空容器,容器內的 eth0 會隨網路命名空間一起被清除,A 介面也被自動從 docker0 卸載。

使用者可以使用 ip netns exec 命令來在指定網路命名空間中進行設定,從而設定容器內的網路。